package org.suyafei.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import javassist.expr.NewArray;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.suyafei.config.RabbitMQConfig;
import org.suyafei.constant.RedisKey;
import org.suyafei.controller.request.TrafficPageRequest;
import org.suyafei.controller.request.UseTrafficRequest;
import org.suyafei.enums.BizCodeEnum;
import org.suyafei.enums.EventMessageTypeEnum;
import org.suyafei.enums.TrafficTaskEnum;
import org.suyafei.exception.BizException;
import org.suyafei.feign.ProductFeignService;
import org.suyafei.feign.ShortLinkFeignService;
import org.suyafei.interceptor.LoginInterceptor;
import org.suyafei.manager.TrafficManager;
import org.suyafei.manager.TrafficTaskManager;
import org.suyafei.model.EventMessage;
import org.suyafei.model.LoginUser;
import org.suyafei.model.TrafficDO;
import org.suyafei.model.TrafficTaskDO;
import org.suyafei.service.TrafficService;
import org.suyafei.utils.BeanUtil;
import org.suyafei.utils.JsonData;
import org.suyafei.utils.JsonUtil;
import org.suyafei.utils.TimeUtil;
import org.suyafei.vo.ProductVO;
import org.suyafei.vo.TrafficVO;
import org.suyafei.vo.UseTrafficVO;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 宿某人
 * @Date: 2022/03/11/16:17
 * @Description:
 */
@Service
@Slf4j
public class TrafficServiceImpl implements TrafficService {

    @Autowired
    private TrafficManager trafficManager;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private TrafficTaskManager trafficTaskManager;

    @Autowired
    private RabbitMQConfig rabbitMQConfig;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ShortLinkFeignService shortLinkFeignService;
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void handleTrafficMessage(EventMessage eventMessage) {
        String messageType = eventMessage.getEventMessageType();
        Long accountNo = eventMessage.getAccountNo();
        if (EventMessageTypeEnum.PRODUCT_ORDER_PAY.name().equalsIgnoreCase(messageType)) {
            //已支付
            String content = eventMessage.getContent();
            Map<String,Object> orderInfoMap = JsonUtil.json2Obj(content, Map.class);

            String outTradeNo = (String) orderInfoMap.get("outTradeNo");
            Integer buyNum = (Integer) orderInfoMap.get("buyNum");
            String productStr = (String) orderInfoMap.get("product");
            ProductVO productVO = JsonUtil.json2Obj(productStr, ProductVO.class);
            log.info("商品信息：{}", productVO);

            //流量包有效天数
            LocalDateTime expiredDateTime = LocalDateTime.now().plusDays(productVO.getValidDay());

            Date date = Date.from(expiredDateTime.atZone(ZoneId.systemDefault()).toInstant());

            //构建TrafficDO
            TrafficDO trafficDO = TrafficDO.builder()
                    .accountNo(accountNo)
                    .dayLimit(productVO.getDayTimes() * buyNum)
                    .dayUsed(0)
                    .totalLimit(productVO.getTotalTimes())
                    .pluginType(productVO.getPluginType())
                    .level(productVO.getLevel())
                    .productId(productVO.getId())
                    .outTradeNo(outTradeNo)
                    .expiredDate(date).build();


            int rows = trafficManager.add(trafficDO);

            log.info("消费新增流量包；rows={},trafficDO={}", rows,trafficDO);
            //新增流量包应该删除该key
            String totalTrafficTimeKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
            redisTemplate.delete(totalTrafficTimeKey);
        } else if (EventMessageTypeEnum.TRAFFIC_FREE_INIT.name().equalsIgnoreCase(messageType)) {
            Long productId = Long.valueOf(eventMessage.getBizId());
            JsonData jsonData = productFeignService.detail(productId);

            ProductVO productVO = jsonData.getData(new TypeReference<ProductVO>(){});

            TrafficDO trafficDO = TrafficDO.builder()
                    .accountNo(accountNo)
                    .dayLimit(productVO.getDayTimes())
                    .totalLimit(productVO.getTotalTimes())
                    .pluginType(productVO.getPluginType())
                    .level(productVO.getLevel())
                    .productId(productVO.getId())
                    .outTradeNo("free_init")
                    .expiredDate(new Date())
                    .build();
            trafficManager.add(trafficDO);

        } else if (EventMessageTypeEnum.TRAFFIC_USED.name().equalsIgnoreCase(messageType)) {
            //流量包使用，检查是否成功使用
            Long trafficTaskId = Long.valueOf(eventMessage.getBizId());
            //检查task是否存在
            TrafficTaskDO trafficTaskDO = trafficTaskManager.findByIdAndAccountNo(trafficTaskId, accountNo);
            if (trafficTaskDO != null && TrafficTaskEnum.LOCK.name().equalsIgnoreCase(trafficTaskDO.getLockState())) {
                //检查短链是否成功
                JsonData jsonData = shortLinkFeignService.check(trafficTaskDO.getBizId());
                //如果不成功，则恢复流量包
                if (jsonData.getCode() != 0) {
                    log.error(("创建短链失败，流量包回滚"));

                    String useDateStr = TimeUtil.format(trafficTaskDO.getGmtCreate(), "yyyy-MM-dd");
                    trafficManager.releaseUsedTimes(accountNo, trafficTaskDO.getTrafficId(), 1,useDateStr);

                    //删除redis 缓存里面的key
                    String totalTrafficTimeKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
                    redisTemplate.delete(totalTrafficTimeKey);
                }
                //可以逻辑删除，定时后续删除
                trafficTaskManager.deleteByIdAndAccountNo(trafficTaskId, accountNo);
            }
        }
    }

    @Override
    public Map<String, Object> pageAvailable(TrafficPageRequest request) {

        int page = request.getPage();
        int size = request.getSize();

        LoginUser loginUser = LoginInterceptor.threadLocal.get();

        IPage<TrafficDO> trafficDOIPage = trafficManager.pageAvailable(page, size, loginUser.getAccountNo());
        List<TrafficDO> records = trafficDOIPage.getRecords();
        List<TrafficVO> trafficVOList = records.stream().map(obj -> BeanUtil.beanProcess(obj, TrafficVO.class)).collect(Collectors.toList());
        Map<String, Object> map = new HashMap<>();

        map.put("total_record", trafficDOIPage.getTotal());
        map.put("total_page", trafficDOIPage.getPages());
        map.put("current_data", trafficVOList);
        return map;
    }

    /**
     * 扣减流量包
     * * 查询用户全部可用流量包
     * * 遍历用户可用流量包
     *   * 判断是否更新-用日期判断
     *     * 没更新的流量包后加入【待更新集合】中
     *       * 增加【今天剩余可用总次数】
     *     * 已经更新的判断是否超过当天使用次数
     *       * 如果没超过则增加【今天剩余可用总次数】
     *       * 超过则忽略
     *
     * * 更新用户今日流量包相关数据
     * * 扣减使用的某个流量包使用次数
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public JsonData reduce(UseTrafficRequest request) {
        Long accountNo = request.getAccountNo();

        //处理流量包，筛选未更新流量包，当前使用的流量包
        UseTrafficVO useTrafficVO = processTrafficList(accountNo);

        log.info("今天可用总次数:{},当前使用流量包{}", useTrafficVO.getDayTotalLeftTimes(), useTrafficVO.getCurrentTrafficDO());

        if (useTrafficVO.getCurrentTrafficDO() == null) {
            return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }
        log.info("待更新流量包列表：{}", useTrafficVO.getUnUpdatedTrafficIds());

        if (useTrafficVO.getUnUpdatedTrafficIds().size() > 0) {
            //更新今日流量包
            trafficManager.batchUpdateUsedTimes(accountNo, useTrafficVO.getUnUpdatedTrafficIds());
        }
        //先更新在扣减
        int rows = trafficManager.addDayUsedTimes(accountNo, useTrafficVO.getCurrentTrafficDO().getId(), 1);

        TrafficTaskDO trafficTaskDO = TrafficTaskDO.builder()
                .accountNo(accountNo)
                .bizId(request.getBizId())
                .useTimes(1)
                .trafficId(useTrafficVO.getCurrentTrafficDO().getId())
                .lockState(TrafficTaskEnum.LOCK.name()).build();

        int add = trafficTaskManager.add(trafficTaskDO);



        if (rows != 1) {
            throw new BizException(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }
        //往redis设置下总流量包次数，短链服务那边递减即可；如果有新增流量包，则删除改key
        Integer leftSeconds = TimeUtil.getRemainSecondsOneDay(new Date());
        String totalTrafficTimeKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
        redisTemplate.opsForValue().set(totalTrafficTimeKey,
                useTrafficVO.getDayTotalLeftTimes() - 1, leftSeconds, TimeUnit.SECONDS);



        //发送延迟消息用于异常回滚
        rabbitTemplate.convertAndSend(rabbitMQConfig.getTrafficEventExchange(), rabbitMQConfig.getTrafficReleaseDelayRoutingKey(),
                EventMessage.builder().accountNo(accountNo).bizId(trafficTaskDO.getId() + "")
                .eventMessageType(EventMessageTypeEnum.TRAFFIC_USED.name()).build());



        return JsonData.buildSuccess();
    }

    /**
     * //处理流量包，筛选未更新流量包，当前使用的流量包
     * @param accountNo
     * @return
     */
    private UseTrafficVO processTrafficList(Long accountNo) {
        List<TrafficDO> trafficDOS = trafficManager.selectAvailableTraffics(accountNo);
        if (CollectionUtils.isEmpty(trafficDOS)) {
            throw new BizException(BizCodeEnum.TRAFFIC_EXCEPTION);
        }
        //天剩余可用总次数
        Integer dayTotalLeftTimes = 0;

        //当前使用
        TrafficDO currentTrafficDO = null;

       List<Long> unUpdatedTrafficIds = new ArrayList<>();


        String todayStr = TimeUtil.format(new Date(), "yyyy-MM-dd");

        for (TrafficDO trafficDO : trafficDOS) {
            String trafficUpdateDate = TimeUtil.format(trafficDO.getGmtModified(), "yyyy-MM-dd");
            if (todayStr.equalsIgnoreCase(trafficUpdateDate)) {
                //已经更新 天剩余可用总次数 - 已用
                int dayLeftTimes = trafficDO.getDayLimit() - trafficDO.getDayUsed();
                dayTotalLeftTimes = dayTotalLeftTimes + dayLeftTimes;

                //选取当次使用流量包
                if (dayLeftTimes > 0 && currentTrafficDO == null) {
                    currentTrafficDO = trafficDO;
                }
            }else {
                dayTotalLeftTimes = dayTotalLeftTimes + trafficDO.getDayLimit();

                //记录未更新的流量包
                unUpdatedTrafficIds.add(trafficDO.getId());
                if (currentTrafficDO == null) {
                    currentTrafficDO = trafficDO;
                }
            }
        }
        UseTrafficVO useTrafficVO = new UseTrafficVO(dayTotalLeftTimes, currentTrafficDO, unUpdatedTrafficIds);
        return useTrafficVO;

    }

    @Override
    public TrafficVO detail(Long trafficId) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        TrafficDO trafficDO = trafficManager.findByIdAndAccountNo(trafficId, accountNo);
        return BeanUtil.beanProcess(trafficDO, TrafficVO.class);
    }

    @Override
    public boolean deleteExpiredTraffic() {
        return trafficManager.deleteExpiredTraffic();
    }
}
